using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.BuildEngine;
using Microsoft.Xna.Framework.Content.Pipeline;
using System.IO;
using System.Diagnostics;

namespace XnaDevRu.ContentBuilder.Framework
{
    /// <summary>
    /// Represents wrapper of MSBuild Project for easy use with XNA Content Pipeline.
    /// </summary>
    public class ContentProject
    {
        private string _xnaInstallPath;
		private string _tempImporterName;
        private Project _project;
        private Engine _engine;
        private BuildItemGroup _contentGroup;
        private BuildItemGroup _pipelineGroup;
        private PipelineAssemblyManager _pipelines = new PipelineAssemblyManager();
        private List<ContentFile> _contentFiles = new List<ContentFile>();
		private ContentProjectOptions _options = new ContentProjectOptions();
        
		/// <summary>
		/// Initializes new instance of the ContentProject.
		/// </summary>
		/// <exception cref="ContentBuilderException">If XNA isn't installed or couldn't access registry.</exception>
        public ContentProject()
        {
            try
            {
                object keyValue = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Microsoft").OpenSubKey(".NETFramework").
                    OpenSubKey("v2.0.50727").OpenSubKey("AssemblyFoldersEx").OpenSubKey("Xna Framework for x86 (v3.0)").GetValue("");
                _xnaInstallPath = keyValue.ToString();
            }
            catch (System.Security.SecurityException securityException)
            {
                throw new ContentBuilderException("Could not access registry.", securityException);
            }
            catch (ArgumentException argumentException)
            {
                throw new ContentBuilderException("Could not find XNA installation directory. Check that XNA is installed.", argumentException);
            }

			InitDefaultOptions();

            _engine = new Engine(new FileInfo(new Uri(typeof(int).Assembly.CodeBase).LocalPath).DirectoryName);
            _project = new Project(_engine);

            // Adding standart Content Pipeline Importers
            _pipelines.AddAssembly(_xnaInstallPath + @"Microsoft.Xna.Framework.Content.Pipeline.EffectImporter.dll");
            _pipelines.AddAssembly(_xnaInstallPath + @"Microsoft.Xna.Framework.Content.Pipeline.FBXImporter.dll");
            _pipelines.AddAssembly(_xnaInstallPath + @"Microsoft.Xna.Framework.Content.Pipeline.TextureImporter.dll");
            _pipelines.AddAssembly(_xnaInstallPath + @"Microsoft.Xna.Framework.Content.Pipeline.XImporter.dll");
            
            // Adding standart Content Pipeline Processors
            // HACK: as we don't wan't to include standart content pipeline assmebly in our project file
            // we may wan't to add processors from it and then remove it from the assembly list.
            _pipelines.AddAssembly(_xnaInstallPath + @"Microsoft.Xna.Framework.Content.Pipeline.dll");
            PipelineAssemblyManager.AssembliesCollection.Remove(_xnaInstallPath + @"Microsoft.Xna.Framework.Content.Pipeline.dll");

			// Hardcode XACT support
			ContentImporterAttribute xactAttribute = new ContentImporterAttribute(".xap");
			xactAttribute.DefaultProcessor = "XactProcessor";
			xactAttribute.DisplayName = "XACT Project - XNA Framework";
			PipelineAssemblyManager.ImporterAttributeCollection.Add(xactAttribute);
			PipelineAssemblyManager.ImporterCollection.Add("XactImporter");
			PipelineAssemblyManager.ProcessorNameCollection.Add("XACT Project - XNA Framework");
			PipelineAssemblyManager.ProcessorsCollection.Add("XactProcessor");
        }

		/// <summary>
		/// Gets the XNA installation path.
		/// </summary>
        public string XnaInstallPath { get { return _xnaInstallPath; } }
		/// <summary>
		/// Gets original MSBuild project instance.
		/// </summary>
        public Project OriginalProject { get { return _project; } }
		/// <summary>
		/// Gets PipelineAssemblyManager associated with this project.
		/// </summary>
        public PipelineAssemblyManager ContentAssemblies { get { return _pipelines; } }
		/// <summary>
		/// Gets collection of the content files associated with this project.
		/// </summary>
        public List<ContentFile> ContentFiles { get { return _contentFiles; } }
		/// <summary>
		/// Gets options of this project.
		/// </summary>
		public ContentProjectOptions ProjectOptions { get { return _options; } }
		/// <summary>
		/// Gets available content files' extension formated to use as filter.
		/// </summary>
        public string ContentExtensionsFilter
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                foreach (ContentImporterAttribute att in PipelineAssemblyManager.ImporterAttributeCollection)
                {
                    string ex = "";
                    foreach(string s in att.FileExtensions)
                    {
                        ex += '*' + s + ';';
                    }
                    if (ex.Length == 0)
                        ex = "*.*;";
                    ex = ex.Substring(0, ex.Length - 1);

                    sb.AppendFormat("{0} ({1})|{1}|", att.DisplayName, ex);
                }
                return sb.ToString() + "All files (*.*)|*.*";
            }
        }

		/// <summary>
		/// Initializes new content file and associates it wth this project.
		/// </summary>
		/// <param name="fileName">Filename of the content file.</param>
		/// <returns>New initialized ContentFile.</returns>
		/// <remarks>Importer and Processor are automatically assigned by the file extension.</remarks>
        public ContentFile InitContentFile(string fileName)
        {
            ContentFile cf = new ContentFile(fileName);
            _contentFiles.Add(cf);
            System.IO.FileInfo fi = new System.IO.FileInfo(fileName);
            ContentImporterAttribute attributeNoExtension = null;
            foreach (ContentImporterAttribute att in PipelineAssemblyManager.ImporterAttributeCollection)
            {
				bool isExtension = false;
                foreach (string s in att.FileExtensions)
                {
					isExtension = true;
                    if (string.Compare(fi.Extension, s, true) == 0)
                    {
                        cf.Importer = att.DisplayName;
						if (att.DefaultProcessor != null)
						{
							int index = PipelineAssemblyManager.ProcessorsCollection.IndexOf(att.DefaultProcessor);
							cf.Processor = PipelineAssemblyManager.ProcessorNameCollection[index];
						}
						else
						{
							int index = PipelineAssemblyManager.ProcessorsCollection.IndexOf("PassThroughProcessor");
							cf.Processor = PipelineAssemblyManager.ProcessorNameCollection[index];

						}
                        return cf;
                    }
                }
				if (!isExtension)
                    attributeNoExtension = att;
            }
            cf.Importer = attributeNoExtension.DisplayName;
			if (attributeNoExtension.DefaultProcessor != null)
			{
				int indexNoExtension = PipelineAssemblyManager.ProcessorsCollection.IndexOf(attributeNoExtension.DefaultProcessor);
				cf.Processor = PipelineAssemblyManager.ProcessorNameCollection[indexNoExtension];
			}
			else
			{
				int indexNoExtension = PipelineAssemblyManager.ProcessorsCollection.IndexOf("PassThroughProcessor");
				cf.Processor = PipelineAssemblyManager.ProcessorNameCollection[indexNoExtension];

			}
            return cf;
        }

		/// <summary>
		/// Loads project file.
		/// </summary>
		/// <param name="projectFileName">Project file name.</param>
		/// <exception cref="InvalidProjectFileException">If file isn't valid project file.</exception>
        public void Load(string projectFileName)
        {
            try
            {
                _pipelines = new PipelineAssemblyManager();
                _contentFiles = new List<ContentFile>();

                _project.Load(projectFileName);
                _pipelineGroup = _project.GetEvaluatedItemsByName(@"PipelineAssembly");
                _contentGroup = _project.GetEvaluatedItemsByName(@"Content");

                foreach (BuildItem item in _pipelineGroup)
                {
                    _pipelines.AddAssembly(item.Include);
                }
                foreach (BuildItem item in _contentGroup)
                {
                    ContentFile cf = new ContentFile(item.Include);
					// HACK: get importer long name from it's class name
					int indexImp = PipelineAssemblyManager.ImporterCollection.IndexOf(item.GetMetadata(@"Importer"));
                    cf.Importer = PipelineAssemblyManager.ImporterAttributeCollection[indexImp].DisplayName;
                    // HACK: get processor long name from it's class name
                    int indexProc = PipelineAssemblyManager.ProcessorsCollection.IndexOf(item.GetMetadata(@"Processor"));
                    cf.Processor = PipelineAssemblyManager.ProcessorNameCollection[indexProc];
					cf.OutputName = item.GetMetadata(@"Name");
                    _contentFiles.Add(cf);
                }

				Target target = _project.Targets["Build"];
				foreach (BuildTask task in target)
				{
					if (task.Name == "BuildContent")
					{
						_options.IntermediateDirectory = task.GetParameterValue(@"IntermediateDirectory");
						_options.OutputDirectory = task.GetParameterValue(@"OutputDirectory");
						_options.RootDirectory = task.GetParameterValue(@"RootDirectory");
						_options.TargetPlatform = task.GetParameterValue(@"TargetPlatform");
					}
				}
            }
            catch (InvalidProjectFileException)
            {
                throw;
            }
        }

		/// <summary>
		/// Saves project to a file.
		/// </summary>
		/// <param name="projectFileName">Project file name.</param>
        public void Save(string projectFileName)
        {
            UpdateProject();
            _project.Save(projectFileName);
        }

		/// <summary>
		/// Registers new logger associated with this project.
		/// </summary>
		/// <param name="logger">Logger instance.</param>
        public void RegisterLogger(Microsoft.Build.Framework.ILogger logger)
        {
            _engine.RegisterLogger(logger);
        }

		/// <summary>
		/// Builds project in single thread.
		/// </summary>
		/// <returns>True if build succeded.</returns>
        public bool Build()
        {
            return Build(false);
        }

		/// <summary>
		/// Builds project.
		/// </summary>
		/// <param name="useThread">True to build in other thread.</param>
		/// <returns>True if build succeded. If in other thread always true.</returns>
        public bool Build(bool useThread)
        {
            UpdateProject();

            if (useThread)
            {
                System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(BuildInThread));
                thread.Start();
                return true;
            }
            else
            {
                return _project.Build(@"Build");
            }
        }

        private void BuildInThread()
        {
            lock (_project)
            {
                _project.Build(@"Build");
            }
        }

        private void UpdateProject()
        {
            // Remove all old data
            _engine.UnloadAllProjects();
            _project = new Project(_engine);

            // Using our tasks
            _project.AddNewUsingTaskFromAssemblyName(@"BuildContent", @"Microsoft.Xna.Framework.Content.Pipeline, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d");
            _project.AddNewUsingTaskFromAssemblyName(@"BuildXact", @"Microsoft.Xna.Framework.Content.Pipeline, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d");

            // Add our Content Pipeline Assemblies
            _pipelineGroup = _project.AddNewItemGroup();
            foreach (string s in _pipelines.Assemblies)
            {
                _pipelineGroup.AddNewItem(@"PipelineAssembly", s);
            }

            // Add our Content Files
            _contentGroup = _project.AddNewItemGroup();
            foreach (ContentFile cf in _contentFiles)
            {
                BuildItem content = _contentGroup.AddNewItem(@"Content", cf.FullPath);
				// HACK: get index from importer long name to get class name.
				_tempImporterName = cf.Importer;
				int indexImp = PipelineAssemblyManager.ImporterAttributeCollection.FindIndex(new Predicate<ContentImporterAttribute>(FindImporterByName));
                content.SetMetadata(@"Importer", PipelineAssemblyManager.ImporterCollection[indexImp]);
                // HACK: just get index from processor long name to get class name.
                int indexProc = PipelineAssemblyManager.ProcessorNameCollection.IndexOf(cf.Processor);
                content.SetMetadata(@"Processor", PipelineAssemblyManager.ProcessorsCollection[indexProc]);
                content.SetMetadata(@"RotationX", "-37");
                content.SetMetadata(@"RotationY", "37");
                content.SetMetadata(@"RotationZ", "7");
				// set the output name of our content file
				content.SetMetadata(@"Name", cf.OutputName);
            }

            // Add our Content processing to know what items are Xact and what items not.
			Target contentTarget = _project.Targets.AddNewTarget(@"_BuildXNAContentLists");
			BuildTask contentTask = contentTarget.AddNewTask(@"CreateItem");
			contentTask.SetParameterValue(@"Include", @"@(Content)");
			contentTask.Condition = "'%(Content.Importer)' != 'XactImporter'";
			contentTask.AddOutputItem(@"Include", @"XNAContent");

			BuildTask xactTask = contentTarget.AddNewTask(@"CreateItem");
			xactTask.SetParameterValue(@"Include", @"@(Content)");
			xactTask.Condition = "'%(Content.Importer)' == 'XactImporter'";
			xactTask.AddOutputItem(@"Include", @"XACTContent");

			// Add our Build target
            Target xnaTarget = _project.Targets.AddNewTarget(@"Build");
			xnaTarget.DependsOnTargets = "_BuildXNAContentLists";

			// Add our BuildContent task
			BuildTask bt = xnaTarget.AddNewTask(@"BuildContent");
            // Setting some parameters... 
			bt.SetParameterValue(@"SourceAssets", @"@(XNAContent)");
            bt.SetParameterValue(@"PipelineAssemblies", @"@(PipelineAssembly)");
			bt.SetParameterValue(@"IntermediateDirectory", _options.IntermediateDirectory);
			bt.SetParameterValue(@"OutputDirectory", _options.OutputDirectory);
			bt.SetParameterValue(@"RootDirectory", _options.RootDirectory);
            bt.SetParameterValue(@"TargetPlatform", _options.TargetPlatform);

			// Add our BuildXact task
			BuildTask bx = xnaTarget.AddNewTask(@"BuildXact");
			// Setting some parameters... 
			bx.SetParameterValue(@"XactProjects", @"@(XACTContent)");
			bx.SetParameterValue(@"IntermediateDirectory", _options.IntermediateDirectory);
			bx.SetParameterValue(@"OutputDirectory", _options.OutputDirectory);
			bx.SetParameterValue(@"RootDirectory", _options.RootDirectory);
			bx.SetParameterValue(@"TargetPlatform", _options.TargetPlatform);
			bx.SetParameterValue(@"XnaFrameworkVersion", @"v3.0");
        }

		private void InitDefaultOptions()
		{
			FileInfo info = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
			_options.RootDirectory = info.DirectoryName;
			_options.IntermediateDirectory = _options.RootDirectory + @"\obj\";
			_options.OutputDirectory = _options.RootDirectory + @"\bin\";
			_options.TargetPlatform = @"Windows";
		}

		private bool FindImporterByName(ContentImporterAttribute attribute)
		{
			if (_tempImporterName == attribute.DisplayName)
				return true;
			return false;
		}
    }
}
